/* 
 * Copyright (c) 2021, CENTRIN.CIYUN.LTD. All rights reserved.
 */
package com.cory.admin.sys.config;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Map;
import java.util.Random;
import javax.crypto.Cipher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;

/**
 * @author xiongcong
 * @date 2021-09-06
 */
@WebServlet(urlPatterns = "/dataCrypt")
@Slf4j
public class DataCryptServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    
    public static final String FINGER = "dataFinger";
    
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String finger = DataCrypt.getFinger();
        ServletOutputStream sos = resp.getOutputStream();
        HttpSession session = req.getSession();
        session.setAttribute(FINGER, finger);
        
        Map<String, String> ret = Maps.newHashMap();
        ret.put("finger", finger);
        ret.put("key", DataCrypt.PUBLIC_KEY);
        sos.print(JSONObject.toJSONString(ret));
    }
    
    public static class DataCrypt {
        protected final static String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALwcyvYIGmhk+be320JWWsq1OYjiM0lzv8eHGMgSIOMLxzM/g9X7jguNe8thxJXR/CLqcTgsfZzk8E8Sc9+qnSDxNl5f5tga93vRxd5713zAeAGqLiTQnRffdzRmdbsmu5+0/K8mj056VhKh8FdBNzAj7e4iX9i+uBBG/oDmZbTVAgMBAAECgYEAmgNU5NTDkj9B+Pnt6UU8doSjw3+3j+bV2K2yS3QUOvAUus/Ax7x6ktjWxzCXvDY9IfUil2RNv9vtKEAqYLCWjc+lf8PV/yH1b7NEgyeAPBXtAJRoOnmYL2bdPW92kP9KgxJruF6Dz/C5AmMOncsvq8ABD+9Darn4p8dwj2ZC4O0CQQDf/AHmZsQokEItfCy4mHS9UbxbfIhEUv1ApPh/+Sr7NkJkHWYCtBQo+8jKO6zurAZQgWBPD1XX2UE4R+VIiZazAkEA1wAqtMvGhccyRZr+6kpkpDIa8+9jOE+nGUzqTDvgCID6as8AzOONFVVK6m/UUqkhcJ8Qu1pF36BGojy5BX2KVwJBAJSFpbji0hXXupowqfLp3RcgmNbNWAp+QUJZYhJx5cdYbmO2fssyH+AhPT6knYJR/YnqkDM8hv6vKCkqu2YDHjMCQAOA8TE5EOclM+CGghj3VWSHnIDVKdzFD4gOBNNxNlltIKeU8AJmwunSFgJ0CBXAw9a+ANvMwM7AIeaK7sj0HskCQAvxfDCq7gaNx+pfu0FHG8Gix08A/A6foggBl1fVu+L9sr9ZuOQ3HbXnl28F9ewuB9xdjnLUDjp7W7U0pB+vKoQ=";
        protected final static String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8HMr2CBpoZPm3t9tCVlrKtTmI4jNJc7/HhxjIEiDjC8czP4PV+44LjXvLYcSV0fwi6nE4LH2c5PBPEnPfqp0g8TZeX+bYGvd70cXee9d8wHgBqi4k0J0X33c0ZnW7JruftPyvJo9OelYSofBXQTcwI+3uIl/YvrgQRv6A5mW01QIDAQAB";

        private final static String codeSequence = "0123456789abcdefghijklmnopqrstuvwxyz";
        private final static int codeCount = 30;
        
        private static final String CHARSET = "UTF-8";
        private static final String RSA_ALGORITHM = "RSA";
        
        public static String getFinger() {
            Random random = new Random();
            StringBuilder randomCode = new StringBuilder();
            for (int i = 0; i < codeCount; ++i) {
                randomCode.append(codeSequence.charAt(random.nextInt(codeSequence.length())));
            }
            return randomCode.toString();
        }
        
        public static String getData(HttpSession session, String pwd) {
            try {
                pwd = privateDecrypt(pwd, getPrivateKey(PRIVATE_KEY));
                
                String finger = (String)session.getAttribute(FINGER);
                if (StringUtils.isBlank(finger) || !pwd.startsWith(finger))
                    return null;
                
                pwd = pwd.substring(finger.length());
                log.info("解密后密码：{}", pwd);
                return pwd;
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            return "";
        }
        
        private static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
            //通过PKCS#8编码的Key指令获得私钥对象
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
            RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
            return key;
        }
        
        private static String privateDecrypt(String data, RSAPrivateKey privateKey) {
            try {
                Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, privateKey);
                byte[] bs = Base64.decodeBase64(data);
                return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, bs, privateKey.getModulus().bitLength()), CHARSET);
            } catch (Exception e) {
                throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
            }

        }
        
        private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
            int maxBlock = 0;
            if (opmode == Cipher.DECRYPT_MODE) {
                maxBlock = keySize / 8;
            } else {
                maxBlock = keySize / 8 - 11;
            }
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] buff;
            int i = 0;
            try {
                while (datas.length > offSet) {
                    if (datas.length - offSet > maxBlock) {
                        buff = cipher.doFinal(datas, offSet, maxBlock);
                    } else {
                        buff = cipher.doFinal(datas, offSet, datas.length - offSet);
                    }
                    out.write(buff, 0, buff.length);
                    i++;
                    offSet = i * maxBlock;
                }
            } catch (Exception e) {
                throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
            }
            byte[] resultDatas = out.toByteArray();
            IOUtils.closeQuietly(out);
            return resultDatas;

        }

    }
}
